package com.jvm123.minio.service;

import com.jvm123.minio.config.MinioProperties;
import com.jvm123.minio.service.client.MinioClientProvider;
import com.jvm123.minio.service.temp.TempCleanService;
import io.minio.MinioClient;
import io.minio.errors.*;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.xmlpull.v1.XmlPullParserException;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 使用minio的api实现文件的存储获取等功能
 *
 * @author yawn http://jvm123.com
 * 2020/1/15 16:04
 */
@Service
public class MinioFileService {

    private static final Logger LOGGER = LoggerFactory.getLogger(MinioFileService.class);

    @Autowired
    private MinioProperties properties;
    @Autowired
    private TempCleanService tempCleanService;
    @Autowired
    private MinioClientProvider clientProvider;

    private String endpoint;
    private String bucket;
    private String accessKey;
    private String secretKey;
    private String tmpDir;

    @PostConstruct
    private void init() {
        this.endpoint = properties.getEndpoint();
        this.bucket = properties.getBucket();
        this.accessKey = properties.getAccessKey();
        this.secretKey = properties.getSecretKey();
        this.tmpDir = properties.getTmpDir();
        if (properties.getTmpClean()) {
            Date firstTmpCleanTime = properties.getTmpFirstCleanTime();
            if (firstTmpCleanTime == null) {
                firstTmpCleanTime = new Date();
            }
            Long tmpCleanPeriod = properties.getTmpCleanPeriod();
            Long tmpAliveDuration = properties.getTmpAliveDuration();
            initTmpCleaner(firstTmpCleanTime, tmpCleanPeriod, tmpAliveDuration);
        }
    }

    private void initTmpCleaner(Date time, Long period, Long duration) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                tempCleanService.cleanTemp(tmpDir, duration);
            }
        }, time, period);
    }

    private MinioClient connect() {
        MinioClient minioClient = clientProvider.getClient(endpoint, accessKey, secretKey);
        LOGGER.debug("Got the client to minIO server {}.", endpoint);
        return minioClient;
    }

    // service for users below

    public boolean createBucket(String bucketName) {
        try {
            MinioClient connect = connect();
            if (connect.bucketExists(bucketName)) {
                return true;
            } else {
                connect.makeBucket(bucketName);
                return true;
            }
        } catch (IOException | InvalidKeyException | NoSuchAlgorithmException
                | NoResponseException | InvalidResponseException | XmlPullParserException
                | RegionConflictException | InvalidBucketNameException
                | ErrorResponseException | InternalException | InsufficientDataException e) {
            LOGGER.error("error: {}", e.getMessage());
            return false;
        }
    }

    public boolean deleteBucket(String bucketName) {
        try {
            MinioClient connect = connect();
            if (connect.bucketExists(bucketName)) {
                connect.removeBucket(bucketName);
                return true;
            } else {
                return true;
            }
        } catch (IOException | InvalidKeyException | NoSuchAlgorithmException
                | NoResponseException | InvalidResponseException | XmlPullParserException
                | InvalidBucketNameException
                | ErrorResponseException | InternalException | InsufficientDataException e) {
            LOGGER.error("error: {}", e.getMessage());
            return false;
        }
    }

    public String save(File file, String fileName) {
        return save(bucket, file, fileName);
    }

    public boolean delete(String fileName) {
        return delete(bucket, fileName);
    }

    public InputStream getStream(String fileName) {
        return getStream(bucket, fileName);
    }

    public File getFile(String fileName) {
        return getFile(bucket, fileName);
    }

    public void writeTo(String fileName, OutputStream os) throws IOException {
        writeTo(bucket, fileName, os);
    }

    public String save(String bucket, File file) {
        return save(bucket, file, null);
    }

    public String save(String bucket, File file, String destFileName) {
        if (bucket == null || bucket.length() <= 0) {
            LOGGER.error("Bucket name cannot be blank.");
            return null;
        }
        if (destFileName == null || destFileName.length() <= 0) {
            destFileName = file.getName();
        }
        try {
            MinioClient minioClient = connect();
            checkBucket(minioClient, bucket);
            minioClient.putObject(bucket, destFileName, file.getAbsolutePath(), null, null, null, null);
            return destFileName;
        } catch(MinioException | NoSuchAlgorithmException | IOException | XmlPullParserException | InvalidKeyException e) {
            LOGGER.error("error: {}", e.getMessage());
            return null;
        }
    }

    private void checkBucket(MinioClient minioClient, String bucket) throws InvalidBucketNameException,
            NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException,
            NoResponseException, XmlPullParserException, ErrorResponseException, InternalException,
            InvalidResponseException, RegionConflictException {
        boolean isExist = minioClient.bucketExists(bucket);
        if (isExist) {
            LOGGER.info("Bucket {} already exists.", bucket);
        } else {
            minioClient.makeBucket(bucket);
        }
    }

    public String save(String bucket, InputStream is, String destFileName) {
        if (bucket == null || bucket.length() <= 0) {
            LOGGER.error("Bucket name cannot be blank.");
            return null;
        }
        try {
            MinioClient minioClient = connect();
            checkBucket(minioClient, bucket);
            minioClient.putObject(bucket, destFileName, is, null, null, null, null);
            return destFileName;
        } catch(MinioException | NoSuchAlgorithmException | IOException | XmlPullParserException | InvalidKeyException e) {
            LOGGER.error("error: {}", e.getMessage());
            return null;
        }
    }

    public boolean delete(String bucket, String fileName) {
        try {
            MinioClient connect = connect();
            connect.removeObject(bucket, fileName);
            return true;
        } catch (IOException | XmlPullParserException | InternalException | InvalidKeyException
                | NoSuchAlgorithmException | InvalidBucketNameException
                | InvalidArgumentException | InvalidResponseException | NoResponseException
                | InsufficientDataException | ErrorResponseException e) {
            LOGGER.error("error: {}", e.getMessage());
            return false;
        }
    }

    public InputStream getStream(String bucket, String fileName) {
        InputStream is = null;
        try {
            MinioClient minioClient = connect();
            is = minioClient.getObject(bucket, fileName);
        } catch(MinioException | NoSuchAlgorithmException | IOException | XmlPullParserException | InvalidKeyException e) {
            LOGGER.error("error: {}", e.getMessage());
        }
        return is;
    }

    public File getFile(String bucket, String fileName) {
        InputStream is = getStream(bucket, fileName);
        File dir = new File(tmpDir);
        if (!dir.exists() || dir.isFile()) {
            dir.mkdirs();
        }
        File file = new File(tmpDir + fileName);
        try {
            FileUtils.copyToFile(is, file);
        } catch (IOException e) {
            LOGGER.error("error: {}", e.getMessage());
        }
        return file;
    }

    public void writeTo(String bucket, String fileName, OutputStream os) throws IOException {
        InputStream is = getStream(bucket, fileName);
        byte[] bytes = new byte[1024];
        int len;
        while ((len = is.read(bytes)) != -1) {
            os.write(bytes, 0, len);
        }
        os.flush();
    }

}
